package com.fhzn.boss.advice.log;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Stopwatch;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 日志打印
 *
 * @author fhzn
 */
@RequiredArgsConstructor
@Slf4j
public class LogAdvice {

    private final ObjectMapper objectMapper;

    @Pointcut("@annotation(com.fhzn.boss.advice.log.Log)")
    public void logPointcut() {
    }

    @Around("logPointcut()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        Class<?> clazz = pjp.getTarget().getClass();
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method targetMethod = methodSignature.getMethod();
        Object result = null;
        Throwable ex = null;
        Stopwatch sw = Stopwatch.createStarted();
        try {
            result = pjp.proceed();
            sw.stop();
            return result;
        } catch (Throwable e) {
            ex = e;
            throw e;
        } finally {
            log(clazz, targetMethod, args, result, sw, ex);
        }
    }


    /**
     * @param clz      当前class
     * @param method   方法
     * @param args     参数
     * @param result   结果
     * @param timeCost 耗时
     * @param e        异常信息
     */
    protected void log(Class<?> clz, Method method, Object[] args, Object result, Stopwatch timeCost, Throwable e) {
        try {
            StringBuilder sb = new StringBuilder();
            sb.append(clz.getSimpleName()).append(".").append(method.getName()).append(" ");
            sb.append("cost: ").append(timeCost.elapsed(TimeUnit.SECONDS)).append("s ");
            sb.append("Request: [");
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (Objects.isNull(arg)) {
                    sb.append("Null");
                } else if (!isPrintArg(arg)) {
                    sb.append("N/A");
                } else {
                    sb.append(print(arg, 300));
                }
                if (i != args.length - 1) {
                    sb.append(", ");
                }
            }
            sb.append("]; ");
            if (e != null) {
                // 顶层有异常信息记录
                sb.append("Exception: ").append(e.getMessage());
            } else {
                sb.append("Result: ");
                if (Objects.isNull(result)) {
                    sb.append("Null");
                } else if (!isPrintRet(result)) {
                    sb.append("N/A");
                } else {
                    sb.append(print(result, 200));
                }
            }
            log.info(sb.toString());
        } catch (Exception error) {
            log.error("log with error", error);
        }
    }

    protected String print(Object arg, int printLen) throws JsonProcessingException {
        if (arg instanceof String || arg.getClass().isPrimitive()
                || arg instanceof Integer || arg instanceof Double || arg instanceof Float
                || arg instanceof Boolean || arg instanceof Byte || arg instanceof Long || arg instanceof Character) {
            return String.valueOf(arg);
        }
        String data = objectMapper.writeValueAsString(arg);
        if (StringUtils.length(data) > printLen) {
            return StringUtils.substring(data, 0, printLen) + "...";
        }
        return data;
    }

    /**
     * 是否对参数序列化
     *
     * @param arg 参数
     */
    protected boolean isPrintArg(Object arg) {
        if (arg instanceof ServletRequest) {
            return false;
        }
        if (arg instanceof ServletResponse) {
            return false;
        }
        if (arg instanceof ResponseEntity) {
            return false;
        }
        return !(arg instanceof MultipartFile);
    }

    /**
     * 是否对结果序列化
     *
     * @param result 结果
     */
    protected boolean isPrintRet(Object result) {
        return !(result instanceof ResponseEntity);
    }

}
